%{
#include "globals.h"
#define  YYDEBUG 1

Import *import;
Method *method;
IfStatement *ifSt;
%}

%union{
	char	*ident;
	char	*value;
	int		val;
	void	*element;		
}

%token PACKAGE IMPORT
%token CLASS EXTENDS
%token <value> TRUE FALSE NUL

%token <ident> IDENT OBJECT
%token <value> CONSTANT 
%token <value> STRING_LITERAL
%token PTR_OP INC_OP DEC_OP LEFT_OP RIGHT_OP LE_OP GE_OP EQ_OP NE_OP
%token AND_OP OR_OP MUL_ASSIGN DIV_ASSIGN MOD_ASSIGN ADD_ASSIGN
%token SUB_ASSIGN LEFT_ASSIGN RIGHT_ASSIGN AND_ASSIGN
%token XOR_ASSIGN OR_ASSIGN OBJECT

%token STATIC DYNAMIC
%token CHAR SHORT INT LONG SIGNED UNSIGNED FLOAT DOUBLE CONST VOID
%token BOOL 
%token ELLIPSIS

%token CASE DEFAULT IF ELSE SWITCH WHILE DO FOR GOTO CONTINUE BREAK RETURN

%token CODE_BLOCK DECLARATION EXP IFST SWITCHST ARRAY_OP CLASS_OP 
%token PRE_INC_OP PRE_DEC_OP POST_INC_OP POST_DEC_OP		
%token BAND_OP BNOT_OP PLUS_OP MINUS_OP LNOT_OP MUL_OP DIV_OP MOD_OP FUNC_OP
%token POINTER_OP CAST_OP LOW_OP HIG_OP BXOR_OP BOR_OP CON_OP ASSIGN_OP

%start program  
%%    
primary_expression
	: IDENT					{ $<element>$=addExpression((void*)strdup($<ident>1), 0, IDENT); /*printf("### IDENT %s\n", $<ident>1);*/}
	| CONSTANT				{ $<element>$=addExpression((void*)strdup($<value>1), 0, CONSTANT); /*printf("### CONSTANT %s\n", $<value>1);*/}
	| STRING_LITERAL		{ $<element>$=addExpression((void*)strdup($<value>1), 0, STRING_LITERAL); /*printf("### STRING_LITERAL %s\n", $<value>1);*/}
	| '(' expression ')'	{ $<element>$=$<element>2; }
	; 

postfix_expression
	: primary_expression	{ $<element>$=$<element>1; }
	| postfix_expression '.' postfix_expression	{$<element>$=addExpression($<element>1, $<element>3, CLASS_OP);}
	| postfix_expression '[' expression ']' {$<element>$=addExpression($<element>1, $<element>3, ARRAY_OP);}
	| IDENT '(' ')'			{$<element>$=addExpression((void*)$<ident>1, 0, FUNC_OP);}
	| IDENT '(' argument_expression_list ')'	{$<element>$=addExpression($<element>1, $<element>3, FUNC_OP); /*printf("//\t>>%p\n", $<element>3);*/}
	| postfix_expression INC_OP		{$<element>$=addExpression($<element>2, 0, POST_INC_OP);}
	| postfix_expression DEC_OP		{$<element>$=addExpression($<element>2, 0, POST_DEC_OP);}
	;
	
argument_expression_list 
	: assignment_expression  { $<element>$=$<element>1; }
	| argument_expression_list ',' assignment_expression	{ $<element>$=setLastElement($<element>1, $<element>3); }
	;
	 
unary_expression
	: postfix_expression		{$<element>$=$<element>1;}
	| INC_OP unary_expression	{$<element>$=addExpression($<element>2, 0, PRE_INC_OP);}
	| DEC_OP unary_expression	{$<element>$=addExpression($<element>2, 0, PRE_DEC_OP); }
	| unary_operator cast_expression	{$<element>$=addExpression($<element>2, 0, $<val>1);}
	;
	
unary_operator 
	: '&'	{ $<val>$=BAND_OP; }
	| '*'	{ $<val>$=POINTER_OP; }
	| '+'	{ $<val>$=PLUS_OP; }
	| '-'	{ $<val>$=MINUS_OP; }
	| '~'	{ $<val>$=BNOT_OP; }
	| '!'	{ $<val>$=LNOT_OP; }
	;

cast_expression
	: unary_expression	{ $<element>$=$<element>1; }		
	| '(' type_specifier ')' cast_expression	{$<element>$=addExpression((void*)$<ident>2, $<element>4, CAST_OP);}
	;

multiplicative_expression
	: cast_expression	{ $<element>$=$<element>1; }
	| multiplicative_expression '*' cast_expression	{$<element>$=addExpression($<element>1, $<element>3, MUL_OP);}
	| multiplicative_expression '/' cast_expression	{$<element>$=addExpression($<element>1, $<element>3, DIV_OP);}
	| multiplicative_expression '%' cast_expression	{$<element>$=addExpression($<element>1, $<element>3, MOD_OP);}
	;

additive_expression
	: multiplicative_expression	{ $<element>$=$<element>1; }
	| additive_expression '+' multiplicative_expression	{$<element>$=addExpression($<element>1, $<element>3, PLUS_OP);}
	| additive_expression '-' multiplicative_expression	{$<element>$=addExpression($<element>1, $<element>3, MINUS_OP);}
	;

shift_expression
	: additive_expression	{ $<element>$=$<element>1; }
	| shift_expression LEFT_OP additive_expression	{$<element>$=addExpression($<element>1, $<element>3, LEFT_OP);}
	| shift_expression RIGHT_OP additive_expression	{$<element>$=addExpression($<element>1, $<element>3, RIGHT_OP);}
	;

relational_expression
	: shift_expression	{ $<element>$=$<element>1; }
	| relational_expression '<' shift_expression	{$<element>$=addExpression($<element>1, $<element>3, LOW_OP);}
	| relational_expression '>' shift_expression	{$<element>$=addExpression($<element>1, $<element>3, HIG_OP);}
	| relational_expression LE_OP shift_expression	{$<element>$=addExpression($<element>1, $<element>3, LE_OP);}
	| relational_expression GE_OP shift_expression	{$<element>$=addExpression($<element>1, $<element>3, GE_OP);}
	;

equality_expression
	: relational_expression	{ $<element>$=$<element>1; }
	| equality_expression EQ_OP relational_expression	{$<element>$=addExpression($<element>1, $<element>3, EQ_OP);}
	| equality_expression NE_OP relational_expression	{$<element>$=addExpression($<element>1, $<element>3, NE_OP);}
	;

and_expression
	: equality_expression	{ $<element>$=$<element>1; }
	| and_expression '&' equality_expression	{$<element>$=addExpression($<element>1, $<element>3, BAND_OP);}
	;

exclusive_or_expression
	: and_expression	{ $<element>$=$<element>1; }
	| exclusive_or_expression '^' and_expression	{$<element>$=addExpression($<element>1, $<element>3, BXOR_OP);}
	;

inclusive_or_expression
	: exclusive_or_expression	{ $<element>$=$<element>1; }
	| inclusive_or_expression '|' exclusive_or_expression	{$<element>$=addExpression($<element>1, $<element>3, BOR_OP);}
	;

logical_and_expression
	: inclusive_or_expression	{ $<element>$=$<element>1; }
	| logical_and_expression AND_OP inclusive_or_expression	{$<element>$=addExpression($<element>1, $<element>3, AND_OP);}
	;

logical_or_expression
	: logical_and_expression	{ $<element>$=$<element>1; }
	| logical_or_expression OR_OP logical_and_expression	{$<element>$=addExpression($<element>1, $<element>3, OR_OP);}
	;
	
conditional_expression
	: logical_or_expression	{ $<element>$=$<element>1; }
	| logical_or_expression '?' expression ':' conditional_expression	{$<element>$=addExpression($<element>1, (void*)addExpression($<element>3, $<element>5, 0), CON_OP);}
	;
	
assignment_expression
	: conditional_expression	{ $<element>$=$<element>1; }
	| unary_expression assignment_operator assignment_expression	{$<element>$=addExpression($<element>1, $<element>3, $<val>2);}
	;

assignment_operator
	: '='			{ $<val>$=ASSIGN_OP; }
	| MUL_ASSIGN	{ $<val>$=MUL_ASSIGN; }	
	| DIV_ASSIGN	{ $<val>$=DIV_ASSIGN; }
	| MOD_ASSIGN	{ $<val>$=MOD_ASSIGN; }
	| ADD_ASSIGN	{ $<val>$=ADD_ASSIGN; }
	| SUB_ASSIGN	{ $<val>$=SUB_ASSIGN; }
	| LEFT_ASSIGN	{ $<val>$=LEFT_ASSIGN; }
	| RIGHT_ASSIGN	{ $<val>$=RIGHT_ASSIGN; }
	| AND_ASSIGN	{ $<val>$=AND_ASSIGN; }
	| XOR_ASSIGN	{ $<val>$=XOR_ASSIGN; }
	| OR_ASSIGN		{ $<val>$=OR_ASSIGN; }
	;

expression
	: assignment_expression	{ $<element>$=$<element>1; }
/*	| expression ',' assignment_expression	*/
	;

expression_statement
	: expression ';' { $<element>$=$<element>1; }
	;

selection_statement
	: IF '(' expression ')' statement {
						ifSt=NEW(IfStatement);
						ifSt->expression=$<element>3;
						ifSt->true=$<element>5;
						ifSt->false=0;	
						$<element>$=addBaseElement(IFST, ifSt);
					}
	| IF '(' expression ')' statement ELSE statement	{
						ifSt=NEW(IfStatement);
						ifSt->expression=$<element>3;
						ifSt->true=$<element>5;
						ifSt->false=$<element>7;	
						$<element>$=addBaseElement(IFST, ifSt);
					}
	| SWITCH '(' expression ')' statement
	;

jump_statement
	: CONTINUE ';'
	| BREAK ';'
	| RETURN ';'
	| RETURN expression ';'
	;


statement
	: code_block_statement	{ $<element>$=$<element>1; }
	| selection_statement	{ $<element>$=$<element>1; }
	| expression_statement	{ $<element>$=$<element>1; }
	| jump_statement	{ $<element>$=$<element>1; }
	;

code_block_item				
	: declaration_statement	{ $<element>$=addBaseElement(DECLARATION, $<element>1); }
	| statement				{ $<element>$=$<element>1;}
	;
	
code_block
	: code_block_item				{ $<element>$=$<element>1; }
	| code_block code_block_item	{ $<element>$=setLastElement($<element>1, $<element>2); }
	;

code_block_statement
	: '{' '}'				{ $<element>$=0; }
	| '{' code_block '}'	{ $<element>$=$<element>2; }
	;

external_declaration
	: function_definition
	| constructor_definition
	| declaration_statement	{ program->class->vars=setLastVariable(program->class->vars, $<element>1); }
	;
	
type_specifier
	: VOID		{$<ident>$="void";}
	| CHAR		{$<ident>$="char";}
	| SHORT		{$<ident>$="short";}
	| INT		{$<ident>$="int";}
	| LONG		{$<ident>$="long";}
	| FLOAT		{$<ident>$="float";}
	| DOUBLE	{$<ident>$="double";}
	| BOOL		{$<ident>$="bool";}
	| OBJECT	{$<ident>$=strdup($<ident>1);}
	;
	
declaration
	: type_specifier IDENT { $<element>$=addVariable(getType($<ident>1), $<ident>2);}
	| type_specifier '*' IDENT	{ $<element>$=addVariable(strdcat("*", getType($<ident>1)), $<ident>3);}
	| type_specifier '[' ']' IDENT	{ $<element>$=addVariable(strdcat("|", getType($<ident>1)), $<ident>4);}
	; 

variable_declaration
	: declaration { $<element>$=$<element>1; }
	| variable_declaration ',' IDENT	{ $<element>$=setLastVariable($<element>1, addVariable(VAR($<element>1)->type, $<ident>3)); }
	;

declaration_statement
	: variable_declaration ';'	{ $<element>$=$<element>1; }
	;

declaration_list
	: declaration		{ $<element>$=$<element>1; }
	| declaration_list ',' declaration	{ $<element>$=setLastVariable($<element>1, $<element>3);}
	;									
	
constructor_definition
	: IDENT '(' declaration_list ')' code_block_statement	{
				method=addConstructor($<ident>1, $<element>3, $<element>5);
				program->class->constructors=setLastMethod(program->class->constructors, method);
			}
	| IDENT '(' ')' code_block_statement	{
				method=addConstructor($<ident>1, 0, $<element>4);
				program->class->constructors=setLastMethod(program->class->constructors, method);
			}
	;

function_definition
	: type_specifier IDENT '(' declaration_list ')' code_block_statement	{
				method=addMethod($<ident>1, $<ident>2, $<element>4, $<element>6);
				program->class->methods=setLastMethod(program->class->methods, method);
			}
	| type_specifier IDENT '(' ')' code_block_statement	{
				method=addMethod($<ident>1, $<ident>2, 0, $<element>5);
				program->class->methods=setLastMethod(program->class->methods, method);
			}
	;
 
class_body
	: external_declaration
	| class_body external_declaration
	;

class_body_statement
	: '{' '}'
	| '{' class_body '}'
	;

class_definition
	: CLASS IDENT class_body_statement { 
				program->class->name=strdup($<ident>2);
				if(program->class->parent==0) program->class->parent=strdup("Object");
				program->class->fullname=strdcat(program->class->package, strdcat(".", program->class->name));
			}
	;

package
	: PACKAGE IDENT	{ $<ident>$=strdup($<ident>2);}
	| package '.' IDENT	{ $<ident>1=strdcat($<ident>1, ".");
						  $<ident>$=strdcat($<ident>1, $<ident>3);}
	;

package_statement
	: package ';'	{program->class->package=strdup($<ident>1);}
	;
 
import
	: IMPORT IDENT 	{ $<ident>$=strdup($<ident>2);}
	| import '.' IDENT { $<ident>1=strdcat($<ident>1, ".");
						  $<ident>$=strdcat($<ident>1, $<ident>3);}
	;

import_statement
	: import ';' {
				import=addImport($<ident>1);
				program->imports=import;
				$<element>$=import;
			}
	| import '.' '*' ';' {error("Not yet implemented.");}
	| import_statement import ';'	{
				import=addImport($<ident>2);
				$<element>1=setLastImport($<element>1, import);
			}
	;

program
	: class_definition
	| package_statement class_definition
	| import_statement class_definition
	| package_statement import_statement class_definition
	;

%%

void yyerror(char *s){
	error(s);	
}
